<?php
class TemplateCodeGenerator extends ProxyCodeGenerator {

	const MAIN_SCOPE = 1;
	const MACRO_SCOPE = 2;
	
	private $resource;

	private $mainGenerator = null;
	private $macroGenerator = null;
	private $scope;
	private $macros;
	
	public function __construct(Resource $resource) {
		
		$this->resource = $resource;

		$this->scope = self::MAIN_SCOPE;
		$this->macros = array();
		$this->mainGenerator = $this->createGenerator(self::MAIN_SCOPE);
	}
	
	private function createGenerator($scope) 
	{
		$options = new CodeGeneratorOptions();
		$options->indentLevel = ($scope == self::MAIN_SCOPE) ? 2 : 1;
		
		return new BufferedCodeGenerator(new CodeGenerator($options));
	}
	
	public function startMacro($macroName) {
		$this->appendCustomCode("\$rs->registerMacro(\$this, '$macroName');");
		$this->macroGenerator = $this->createGenerator(self::MACRO_SCOPE);
		$this->scope = self::MACRO_SCOPE;
	}
	
	public function endMacro() {
		$this->macros[] = $this->macroGenerator;
		$this->scope = self::MAIN_SCOPE;
	}
	
	public function output() {
		
		$this->generator()->flush();
		
		// skeleton
		$skeleton = "<?php\n";
		$skeleton .= "\$GLOBALS['gery.template.className'] = '__TEMPLATEID__';\n\n";
		$skeleton .= "final class __TEMPLATEID__ extends TemplateBase {\n\n";
		$skeleton .= "	public static function getResourceName() {\n";
		$skeleton .= "		return '" . $this->resource->getName() . "';\n";
		$skeleton .= "	}\n\n";
		$skeleton .= "	// main function\n";
		$skeleton .= "	public function main(IGeryContext \$outerContext, RuntimeServices \$rs) {\n";
		$skeleton .= "		\$context = new GeryTemplateContext(\$outerContext);\n";
		$skeleton .= "		\$rs->pushTemplateName(\$this->getResourceName());\n";
		$skeleton .= "		\$output = '';\n";
		$skeleton .= "%s\n";
		$skeleton .= "		\$rs->popTemplateName();\n";
		$skeleton .= "		return \$output;\n";
		$skeleton .= "	}\n\n";
		$skeleton .= "	// macros\n";
		$skeleton .= "%s\n";
		$skeleton .= "	public function getLastModified() {\n";
		$skeleton .= "		return " . $this->resource->getLastModified() . ";\n";
		$skeleton .= "	}\n";
		$skeleton .= "}\n";
		$skeleton .= "?>";
		
		$macroCode = '';
		
		foreach ($this->macros as $macroGenerator) {
			$macroCode .= $macroGenerator->output() . "\n\n";
		}
		
		return sprintf($skeleton, parent::output(), $macroCode);
	}
	
	public function generator() {
		return ($this->scope == self::MACRO_SCOPE)
			? $this->macroGenerator
			: $this->mainGenerator;
	}
	
	
	//
	// Some static helper functions
	//
	
	/**
	 * Returns a string that contains valid PHP code in the form of an array
	 * which represents a list of modifiers
	 * 
	 * Each modifier is represtented as an array:
	 *  
	 *   array(
	 *     0 => name
	 *     ,1 => array(parameters)
	 *     ,2 => array(indexes)
	 *     ,3 => line number
	 *   )
	 *
	 * @param ASTModifier[] $modifierNodes
	 * @param unknown_type $compileEngine
	 * @return array
	 */
	public static function createModifiers($modifierNodes, $compileEngine) {
		
		$output = '';
		
		foreach ($modifierNodes as $modifierNode) {
			
			// get the index
			$index = 'array(';
			
			foreach ($modifierNode->index as $indexExpr) {
				$compiler = new ExpressionVisitor($compileEngine);
				$indexExpr->accept($compiler);
				
				$index .= $compiler->getOutput() . ',';
			}
			
			$index .= ')';
		
			// handle the parameters
			if ($modifierNode->parameters != null) {
				$parameterOutput = 'array(';

				foreach ($modifierNode->parameters as $parameter) {
					$compiler = new ExpressionVisitor($compileEngine);
					$parameter->accept($compiler);
					
					$parameterOutput .= $compiler->getOutput() . ',';
				}
				
				$parameterOutput .= ')';
				
				$output .= sprintf("array('%s', %s, %s, %d),"
					,$modifierNode->name
					,$parameterOutput
					,$index
					,$modifierNode->line
					);
			} else {
				$output .= sprintf("array('%s', null, %s, %d),", $modifierNode->name, $index, $modifierNode->line);
			}
		}
		
		return 'array(' . $output . ')';
	}
	
	/**
	 * Returns a representation of a reference
	 * 
	 * array(
	 *   0 => {name}
	 *   ,1 => {silent}
	 *   ,2 => {smurk}
	 *   ,3 => {modifiers}
	 * )
	 *
	 * @param IASTNode $node
	 * @param unknown_type $compileEngine
	 * @return string
	 */
	public static function createReference(IASTNode $node, $compileEngine) {

		// append the reference name, whether it's silent and its smurk
		$output = 'array(' . var_export($node->name, true) . ',' . var_export($node->silent, true) . ',' . var_export($node->smurk, true) . ',';
		
		// apend modifiers
		if (count($node->modifiers)) {
			$output .= self::createModifiers($node->modifiers, $compileEngine);
		} else {
			$output .= 'array()';
		}
		
		// append indexes
		$output .= ',array(';
		if ($node->indexes !== null) {			
			foreach ($node->indexes as $index) {
				
				// compile the index
				$compiler = new ExpressionVisitor($compileEngine);
				$index->accept($compiler);
					
				$output .= $compiler->getOutput() . ',';
			}
		}
		$output .= ')';
		
		// return output
		return $output . ')';
	}
}
?>